﻿using System;
using System.Windows.Input;

namespace Js.ComponentModel
{
    /// <summary>
    /// 명령을 정의합니다.
    /// </summary>
    public class RelayCommand : ICommand
    {
        #region Variables

        private Action _executeAction;
        private Func<bool> _canExecuteFunc;

        #endregion Variables

        #region Event Handler

        /// <summary>
        /// 명령을 실행해야 하는지 여부에 영향을 주는 변경이 발생할 때 발생합니다.
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        #endregion Event Handler

        #region Constructor

        /// <summary>
        /// DelegateCommand 인스턴스를 초기화합니다.
        /// </summary>
        /// <param name="executeAction">명령이 호출될 때 호출될 Action 대리자입니다.</param>
        /// <param name="canExecuteFunc">현재 상태에서 명령을 실행할 수 있는지 여부를 확인하는 Func 대리자입니다.</param>
        public RelayCommand(Action executeAction, Func<bool> canExecuteFunc = null)
        {
            _executeAction = executeAction;
            _canExecuteFunc = canExecuteFunc;
        }

        #endregion Constructor

        #region Method

        /// <summary>
        /// 현재 상태에서 명령을 실행할 수 있는지 여부를 확인하는 메서드를 정의합니다.
        /// </summary>
        /// <param name="parameter">명령에서 사용하는 데이터입니다.명령에서 데이터를 전달할 필요가 없으면 이 개체를 null로 설정할 수 있습니다.</param>
        /// <returns>이 명령을 실행할 수 있으면 true이고, 그렇지 않으면 false입니다.</returns>
        public bool CanExecute(object parameter)
        {
            if(_canExecuteFunc != null)
            {
                return _canExecuteFunc();
            }
            return true;
        }

        /// <summary>
        /// 명령이 호출될 때 호출할 메서드를 정의합니다.
        /// </summary>
        /// <param name="parameter">명령에서 사용하는 데이터입니다.명령에서 데이터를 전달할 필요가 없으면 이 개체를 null로 설정할 수 있습니다.</param>
        public void Execute(object parameter)
        {
            if(_executeAction != null)
            {
                _executeAction();
            }
        }

        /// <summary>
        /// 현재 상태에서 명령을 실행할 수 있는지 여부를 확인합니다.
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            CanExecute(null);
        }

        #endregion Method
    }

    /// <summary>
    /// 명령을 정의합니다.
    /// </summary>
    /// <typeparam name="TSource">명령이 실행될 때 전달되는 parameter의 형식입니다.</typeparam>
    public class RelayCommand<TSource> : ICommand
    {
        #region Variables

        private Action<TSource> _executeAction;
        private Func<bool> _canExecuteFunc;

        #endregion Variables

        #region Event Handler

        /// <summary>
        /// 명령을 실행해야 하는지 여부에 영향을 주는 변경이 발생할 때 발생합니다.
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        #endregion Event Handler

        #region Constructor

        /// <summary>
        /// DelegateCommand 인스턴스를 초기화합니다.
        /// </summary>
        /// <param name="executeAction">명령이 호출될 때 호출될 Action 대리자입니다.</param>
        /// <param name="canExecuteFunc">현재 상태에서 명령을 실행할 수 있는지 여부를 확인하는 Func 대리자입니다.</param>
        public RelayCommand(Action<TSource> executeAction, Func<bool> canExecuteFunc = null)
        {
            _executeAction = executeAction;
            _canExecuteFunc = canExecuteFunc;
        }

        #endregion Constructor

        #region Method

        /// <summary>
        /// 현재 상태에서 명령을 실행할 수 있는지 여부를 확인하는 메서드를 정의합니다.
        /// </summary>
        /// <param name="parameter">명령에서 사용하는 데이터입니다.명령에서 데이터를 전달할 필요가 없으면 이 개체를 null로 설정할 수 있습니다.</param>
        /// <returns>이 명령을 실행할 수 있으면 true이고, 그렇지 않으면 false입니다.</returns>
        public bool CanExecute(object parameter)
        {
            if(_canExecuteFunc != null)
            {
                return _canExecuteFunc();
            }
            return true;
        }

        /// <summary>
        /// 명령이 호출될 때 호출할 메서드를 정의합니다.
        /// </summary>
        /// <param name="parameter">명령에서 사용하는 데이터입니다.명령에서 데이터를 전달할 필요가 없으면 이 개체를 null로 설정할 수 있습니다.</param>
        public void Execute(object parameter)
        {
            if(_executeAction != null)
            {
                _executeAction(parameter != null ? (TSource)parameter : default(TSource));
            }
        }

        /// <summary>
        /// 현재 상태에서 명령을 실행할 수 있는지 여부를 확인합니다.
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            CanExecute(null);
        }

        #endregion Method
    }
}